<?php
/**
 * Class for building an asynchronous request, wich can be fired to a website an handled by the class
 * 
 * @author Michael Schnyder (michael.schnyder@ebyte.ch), eByte
 * @copyright 2009 Michael Schnyder
 *
 */
class AsyncRequest {
	
	private $host;
	private $port;
	private $path;
	private $file;
	private $getVars;
	
	private $variables;
	private $commands;
	private $includes;
	
	private $passPhrase;
	
	public $IsDebug;
	
	public function __construct() {
		
		$this->host = $_SERVER['HTTP_HOST'];
		$this->port = $_SERVER['SERVER_PORT'];
		$this->path = substr(dirname($_SERVER['REQUEST_URI']),1);
		$this->file = "index.php";
		$this->getVars = array("mode"=>"async");
		
		$cfg = Runtime::getConfiguration();
		
		$this->passPhrase = $cfg['async_passphrase']; 
		
		$this->commands = array();
		
		
		$this->includes = get_included_files();
		//Do the funcking URL Encode cause serializion bug
		
		foreach ($this->includes as $k=>$i) {
			$this->includes[$k] = base64_encode($i);
		}
		
	}
	
	/**
	 * Creates an Asyncronous Request
	 *
	 * @param AsyncCommand[] $commands
	 * @param Array[] $variables
	 */
	function createRequest($commands=null, $variables=null) {
		
		$this->addCommand($commands);
		$this->addVariable($variables);
	}
	
	/**
	 * Adds one or multiple (array) Command(s) to the Execution List
	 *
	 * @param AsyncCommand $c
	 */
	function addCommand(AsyncCommand $c) {
		if(is_array($c)) {
			$this->commands = array_merge($this->commands, $c);
		}
		else {
			array_push($this->commands, $c);
		}
	}
	
	/**
	 * Adds a Variable to the variable list
	 *
	 * @param mixed $v
	 */
	function addVariable($name, $v) {
		$this->variables[$name] = $v;
	}
	
	/**
	 * Executes the Request on the Remote Server
	 *
	 */
	function executeRequest() {
		
		/* Create the Request */
		
		//Request String
		$query = "/" . ($this->path?$this->path . "/":"") . $this->file ."?PHPSESSID=".session_id();
		if($this->getVars) {
			while(list($p, $v) = each($this->getVars)) {
				$QueryAdd="&".$p."=".$v;
			}
		}
		$query.=$QueryAdd;
		
		if($this->IsDebug) {
			$query.="&debug=true";
		}
		
		$serdata = serialize($this);
		$serinc = serialize($this->includes);
		
		$datasig = md5($serdata.$serinc . "-" . $this->passPhrase); 
		
		$b64data = urlencode(base64_encode($serdata));
		$b64sig  = urlencode(base64_encode($datasig));
		$b64inc  = urlencode(base64_encode($serinc));
		
		//POST-Data
		$data = gzcompress("d=$b64data&i=$b64inc&s=$b64sig");
		
		$req = "POST $query HTTP/1.1\n";
		$req.= "Host: ".$this->host."\n";
		$req.= "Connection: close\n";
		$req.= "Content-Type: application/x-www-form-urlencoded\n";
		$req.= "Content-Length: " . strlen($data) ."\n";
		$req.= "\n";
		$req.= $data;
		
		$s = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
		socket_connect($s, $this->host, $this->port);
		socket_send($s, $req, strlen($req), 0);
		socket_close($s);
		
		if($this->IsDebug) {
			echo "<!--";
			echo "serdata: $serdata\n";
			echo $req;
			echo "\n----------------------------------------------\n";
			print_r($this);
			echo "-->";
			
			//die();
		}
		
		
	}
	
	//Getters
	public function getCommands() {
		return $this->commands;
	}
	public function getVariables() {
		return $this->variables;
	}
	public function getIncludes() {
		return $this->includes;
	}
}


/**
 * Stores all Information about a specific command inside an AsyncRequest
 */

class AsyncCommand {
	
	private $className; //Can be either an Object (serialized Variable)or a static Object
	
	private $methodName;
	private $methodParameters;
	
	private $isStatic; //Is False when Call is executed as a Method on a Variable wich is sent with
	
	public function __construct($class=null, $method=null, $params=null, $static=false) {
		
		$this->methodParameters = array();
		
		if($class!=null && $method!=null) {
			$this->setClassName($class);
			$this->setMethodName($method);
		
			$this->addMethodParameter($params);
		}
		$this->isStatic = $static;
		
	}
	
	public function setClassName($class) {
		$this->className = $class;
	}
	
	public function setMethodName($method) {
		$this->methodName = $method;
	}
	
	public function addMethodParameter($param) {
		if(is_array($param)) {
			$this->methodParameters = array_merge($this->methodParameters, $param);
		}
		else {
			array_push($this->methodParameters, $param);
		}
	}
	
	public function execute() {
		
		if($this->isStatic) {
			$m = $this->className."::".$this->methodName;
		}
		else {
			$m = array($this->className, $this->methodName);
		}
		
		if(is_callable($m)) {
            return call_user_func_array($m, $this->methodParameters);
		}
		return false;
		
	}
}

/**
 * Used for correct handling of AsyncRequests fired by the class AsynRequest
 *
 */
class AsyncHandler {
	
	private $passPhrase;
	private static $isDebug;
		
	/**
	 * 
	 */
	function __construct() {
		$cfg = Runtime::getConfiguration();
		
		$this->passPhrase = $cfg['async_passphrase'];
	}
	
	function handleRequest($get, $post) {
	
		//Debug?
		AsyncHandler::$isDebug = ($get["debug"]=="true");
		
		AsyncHandler::cls();
		
		/*
		 * Regenerate the Envoirment
		 */
		
		//"Unpack" the Request
		AsyncHandler::debug("Got Request, unzipping");
		
		parse_str(gzuncompress(file_get_contents('php://input')), $data);
		AsyncHandler::debug(print_r($data, true));
		$b64data = $data["d"];
		$b64inc  = $data["i"];
		$b64sig  = $data["s"];
		
		$serdata = urldecode(base64_decode($b64data));
		$serinc  = urldecode(base64_decode($b64inc));
		$datasig = urldecode(base64_decode($b64sig));
		
		//Check consistency of serdata
		AsyncHandler::debug("Calculate signature");
		try {
			$check = md5($serdata.$serinc . "-" . $this->passPhrase);
		} 
		catch (Exception $e) {
			AsyncHandler::debug("There was an Error: " . $e->getMessage());
		}
		if($check==$datasig) {
			AsyncHandler::debug("Signature ok.");
		}
		else {
			AsyncHandler::debug("Signature failed! Check: $check, Sig: $datasig, passphase: $this->passPhrase");
			die("Request failed!");
		}
		
		//Reinclude Files wich are no already included
		$included = get_included_files(); //Already included
		$includes = unserialize($serinc);

		//AsyncHandler::debug(print_r($included, true));
		//AsyncHandler::debug(print_r($includes, true));
		
		foreach ($includes as $file) {
			$file = base64_decode($file);
			
			if(!array_search($file, $included)) {
				AsyncHandler::debug("File $file not included. Exists? " . (file_exists($file)?"Yes":"No"));
				if(file_exists($file)) {
					AsyncHandler::debug ("include $file");
					include_once $file;
				}
			}
			
		}
		
		//Unserialize again, there may be new files included with definitions about data contents
		$req = unserialize($serdata);
		if(!is_object($req)) {
			AsyncHandler::debug("Unserialize of Request failed! Data:\n".$serdata);
			die();
		}
		//AsyncHandler::debug(print_r($req, true));
		
		//Create Variables
		//AsyncHandler::debug(print_r($req->getVariables, true));
		$vars = $req->getVariables();
		if(count($vars)) {
			while(list($name, $value)=each($vars)) {
				AsyncHandler::debug(" - Ceating Variable \$$name.");
				$$name = $value;
			}
		}
		
		//Execute Commands
		AsyncHandler::debug("Processing Commands");
		foreach ($req->getCommands() as $c) {
			if($r = $c->execute()) {
				AsyncHandler::debug("- Command successful executed. Return value: $r");
			}
			else {
				AsyncHandler::debug("- Command not execuded. Method not found!");
			}
		}
		
		AsyncHandler::debug("Finished");
	}
	
	static function cls() {
		if(AsyncHandler::$isDebug)
			@unlink("debug.txt");
	}

	static function debug($msg) {
		if(AsyncHandler::$isDebug)
			file_put_contents("debug.txt", date("H:i:s")." - $msg\n", FILE_APPEND);
	}
}

?>